﻿// (c) Norbert Huffschmid
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ServiceModel;
using log4net;
using NProtocol;
using ShoutBox.Core;
using ShoutBox.Service;
using NProtocol.Codec;

namespace ShoutBox
{
    /// <summary>
    /// ShoutBox proxy
    /// </summary>
    [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
    public class ShoutBoxProxy : IShoutBox, IShoutBoxEndPointControllerCallback
    {
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);


        /// <summary>
        /// Fires when a shutdown request has been received
        /// </summary>
        public event EventHandler ShutDownRequestReceived;


        /// <summary>
        /// Gets a collection of visible shoutboxes in the network
        /// </summary>
        public ObservableCollection<ISessionMember> SessionMembers
        {
            get { return this.sessionMembers; }
        }


        private IShoutBoxEndPointController shoutBoxEndPointController;
        private ObservableCollection<ISessionMember> sessionMembers = new ObservableCollection<ISessionMember>();
        private bool disposed = false;


        /// <summary>
        /// Constructor
        /// </summary>
        public ShoutBoxProxy()
        {
            log.Debug("Constructor");

            DuplexChannelFactory<IShoutBoxEndPointController> pipeFactory = 
                new DuplexChannelFactory<IShoutBoxEndPointController>(
                    this,
                    new NetNamedPipeBinding(),
                    new EndpointAddress("net.pipe://localhost/ShoutBoxEndPointController"));

            this.shoutBoxEndPointController = pipeFactory.CreateChannel();
            this.shoutBoxEndPointController.Subscribe();

            foreach (ISessionMember shoutBox in this.shoutBoxEndPointController.SessionMembers)
            {
                this.sessionMembers.Add(shoutBox);
            }
        }


        /// <summary>
        /// Refresh proxy data
        /// </summary>
        public void Refresh()
        {
            // refresh session member collection
            this.sessionMembers.Clear();
            foreach (ISessionMember shoutBox in this.shoutBoxEndPointController.SessionMembers)
            {
                this.sessionMembers.Add(shoutBox);
            }
        }

        /// <summary>
        /// Callback method when a session member's property has changed
        /// </summary>
        /// <param name="propertyName">Name of changed property</param>
        /// <param name="sessionMember">Session member</param>
        public void OnSessionMemberPropertyChanged(string propertyName, ISessionMember sessionMember)
        {
            foreach (ISessionMember localSessionMember in this.sessionMembers)
            {
                if (sessionMember.Id == localSessionMember.Id)
                {
                    localSessionMember.IsSender = sessionMember.IsSender;
                    localSessionMember.Name = sessionMember.Name;
                }
            }
        }

        /// <summary>
        /// Callback method when a session member has been added
        /// </summary>
        /// <param name="sessionMember">Session member added</param>
        public void OnSessionMembersAdded(ISessionMember sessionMember)
        {
            log.Info("OnSessionMembersAdded: " + sessionMember.ToString());

            this.sessionMembers.Add(sessionMember);
        }

        /// <summary>
        /// Callback method when a session member has been removed
        /// </summary>
        /// <param name="sessionMember">Session member removed</param>
        public void OnSessionMembersRemoved(ISessionMember sessionMember)
        {
            log.Info("OnSessionMembersRemoved: " + sessionMember.ToString());

            // due to marshalling, object instances are just copies of the original session members
            // ==> this.shoutBoxes.Remove(sessionMember) WILL NOT WORK!
            for (int i = 0; i < this.sessionMembers.Count; i++)
            {
                if (this.sessionMembers[i].Id == sessionMember.Id)
                {
                    ISessionMember removedShoutBox = this.sessionMembers[i];

                    if (removedShoutBox.IsSender)
                    {
                        removedShoutBox.IsSender = false;
                    }

                    this.sessionMembers.RemoveAt(i);

                    break;
                }
            }
        }

        /// <summary>
        /// Callback method when session members have been reset
        /// </summary>
        public void OnSessionMembersReset()
        {
            log.Info("OnSessionMembersReset");

            this.sessionMembers.Clear();
        }

        /// <summary>
        /// Callback method when the shoutbox client should shut down
        /// </summary>
        public void OnShutDown()
        {
            log.Info("OnShutDown");

            this.shoutBoxEndPointController = null; // terminate communication with (non-existing) controller

            if (this.ShutDownRequestReceived != null)
                this.ShutDownRequestReceived(this, EventArgs.Empty);
        }

        /// <summary>
        /// Start shout procedure
        /// </summary>
        /// <param name="target">Target of shout operation</param>
        public void StartShout(ISessionMember target)
        {
            log.Debug("StartShout");

            this.shoutBoxEndPointController.StartShout(target);
        }

        /// <summary>
        /// Terminate shout procedure
        /// </summary>
        /// <param name="target">Target of EndOfShout operation</param>
        public void EndOfShout(ISessionMember target)
        {
            log.Debug("EndOfShout");

            this.shoutBoxEndPointController.EndOfShout(target);
        }

        /// <summary>
        /// Stream shout audio data to remote target
        /// </summary>
        /// <param name="target">Target of SetShoutVolume operation</param>
        /// <param name="buffer">Audio buffer</param>
        /// <param name="format">Data format</param>
        public void StreamShout(ISessionMember target, byte[] buffer, RealTimeDataFormat format)
        {
            log.Debug("StreamShout");

            this.shoutBoxEndPointController.StreamShout(target, buffer, format);
        }

        /// <summary>
        /// Release all resources
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        protected virtual void Dispose(bool disposing)
        {
            log.Debug("Dispose");

            if (!disposed)
            {
                if (disposing)
                {
                    if (this.shoutBoxEndPointController != null)
                        this.shoutBoxEndPointController.Unsubscribe();
                }
            }
            disposed = true;
        }
    }
}
